Описание мультиплексора на языке SystemVerilog

Мультипле́ксор — устройство, имеющее несколько сигнальных входов, один или более управляющих входов и один выход. Мультиплексор позволяет передавать сигнал с одного из входов на выход; при этом выбор желаемого входа осуществляется подачей соответствующей комбинации управляющих сигналов[1].

Иными словами, мультиплексор — это переключатель (коммутатор), соединяющий выход с одним из множества входов.

../.pic/Basic%20Verilog%20structures/multiplexor/fig_01.drawio.svg

Для начала создадим простой двухвходовой мультиплексор. Предположим, на Y нам необходимо передать один из сигналов — D0 или D1 в зависимости от значения управляющего сигнала S: когда S==0, на Y подается сигнал D0, в противном случае — D1.

../.pic/Basic%20Verilog%20structures/multiplexors/fig_02.drawio.svg

На языке SystemVerilog это можно описать несколькими способами. Первый — с помощью тернарного условного оператора:

Тернарный условный оператор

О тернарном условном операторе

Операторы бывают различной арности(количества аргументов оператора[операндов]):

  • унарный (с одним операндом), пример: -a;
  • бинарный (с двумя операндами), пример: a+b;
  • тернарный (с тремя операндами), пример: cond ? if_true : false;
  • и др.

Несмотря на то, что тернарным оператором может быть любой оператор, принимающий три операнда, обычно под ним подразумевается тернарный условный оператор, работающий следующим образом:

<условие> ? <значение_если_условие_истинно> : <значение_если_условие_ложно>

Первым операндом идет некоторое условие (любое выражение, которое может быть сведено к 1 или 0). Далее ставится знак вопроса (часть тернарного оператора, отделяющая выражение первого операнда от выражения второго операнда). Далее пишется выражение, которое будет результатом тернарного условного оператора в случае, если условие оказалось истинным. После чего ставится двоеточие (часть тернарного условного оператора, отделяющая выражение второго операнда от выражения третьего операнда). Затем пишется выражение, которое будет результатом тернарного условного оператора в случае, если условие оказалось ложным.

Пример для языка C++:

a = b+c >= 5 ? b+c : b+d;

Сперва вычисляется первый операнд (выражение b+c >= 5). Если это выражение оказалось истинным (равно единице), то переменной a будет присвоено значение второго операнда (выражения b+c), в противном случае переменной a будет присвоено значение третьего операнда (выражения b+d).

logic Y;
assign Y = S==1 ? D1 : D0;

Данное выражение говорит нам, что если S==1, то Y присваивается значение D1, в противном случае — значение D0.

../.pic/Basic%20Verilog%20structures/multiplexors/fig_03.drawio.svg

Также мультиплексор можно описать через конструкцию if-else в блоке always.

Далее будет ключевой параграф сложного для понимания текста, очень важно запомнить, что там написано и разобрать приведенные листинги.




Блок always

Блок always — это специальный блок, который позволяет описывать комбинационные и последовательностные схемы, используя более сложные конструкции, такие как if-else, case. На самом деле, в языке SystemVerilog помимо общего блока always, которым можно описать любой вид логики, существует множество специализированных блоков, предназначенных для описания отдельно комбинационной, синхронной и последовательностной асинхронной логики соответственно:

  • always_comb
  • always_ff
  • always_latch

Мультиплексор можно описать в любом из этих блоков, разница будет лишь в том, к чему именно будет подключен выход мультиплексора: к проводу, регистру, или защелке.

В зависимости от вида always-блока используется один из двух видов присваиваний: блокирующее присваивание (=) и неблокирующего присваивания (<=). Подробно о различиях между присваиваниями рассказано в этом документе. До его прочтения запомните:

  • внутри блока always_ff и always_latch необходимо использовать оператор неблокирующего присваивания (<=);
  • внутри блока always_comb необходимо использовать оператор блокирующего присваивания (=).

Остановитесь на выделенном выше фрагменте документа, пока полностью не разберете его. Без освоения всех описанных выше особенностей языка SystemVerilog вы столкнетесь в будущем с множеством ошибок.



Блок if-else

Реализация мультиплексора через блок if-else похожа на реализацию мультиплексора через тернарный оператор. Если в тернарном операторе управляющий сигнал указывался в качестве первого операнда, отделяемого оператором ?, то в данном блоке управляющий сигнал указывается в скобках после ключевого слова if.

Далее описывается присваивание сигнала, который должен идти на выход при управляющем сигнале равном единице (значение до оператора : в тернарном операторе).

После, в блоке else описывается присваивание сигнала, который должен идти на выход при управляющем сигнале равном нулю (значение после оператора : в тернарном операторе).

logic Y;
always_comb begin // 1) Используется always_comb, т.к. мы хотим подключить
                  // выход мультиплексора к проводу
  if(S) begin     // 2) if-else может находиться только внутри блока always.
    Y = D1;       // 3) Используется оператор блокирующего присваивания.
  end else begin
    Y = D0;
  end
end

Кроме того, важно запомнить, что присваивание сигналу допускается только в одном блоке always.

Неправильно:

logic Y;
always_comb begin
  if(S==1) begin
    Y = D1;
  end
end

always_comb begin
  if(S==0) begin // Нельзя выполнять операцию присваивания
    Y = D0;      // для одного сигнала (Y) в нескольких
  end            // блоках always!
end

Если нарушить это правило, то в будущем (возможно не сразу, но в любом случае — обязательно), возникнет ошибка, которая так или иначе будет связана с multiple drivers.

Будьте очень внимательны при использовании данного блока. Он обманчиво похож на условный блок в языках программирования, из-за чего возникает желание пользоваться им так же, как можно пользоваться условными блоками в языках программирования. Это не так. Обратите внимание на то, что данный блок выше упоминается исключительно как блок if-else. При реализации мультиплексора, у любого блока if должен быть соответствующий блок else, как у тернарного оператора должно быть два выходных операнда. Если не указать блок else при описании мультиплексора, у него будет только один вход, и в итоге на выходе мультиплексора будет сгенерирована защелка. Подробнее о защелках описано здесь.

Существуют ситуации, когда блок if может быть использован без блока else (например, при описании дешифраторов или сигналов разрешения записи). Однако при описании мультиплексоров таких ситуаций не бывает.

case-блок

Мультиплексор также можно описать с использованием конструкции case. Блок case лучше подходит для описания мультиплексора, когда у того более двух входов (ведь в случае конструкции if-else пришлось бы делать вложенное ветвление).

Конструкция case представляет собой инструмент множественного ветвления, который сравнивает значение заданного выражения с множеством вариантов, и, в случае первого совпадения, использует соответствующую ветвь. На случай, если ни один из вариантов не совпадет с заданным выражением, конструкция case поддерживает вариант default. Данная конструкция визуально похожа на оператор switch-case в Си, однако вы должны понимать, что используется она не для написания программы, а описания аппаратуры, в частности мультиплексоров/демультиплексоров и дешифраторов.

Конструкция case, наряду с if-else, может быть описана только в блоке always.

Реализация двухвходового мультиплексора с помощью case может выглядеть так:

logic Y;
always_comb begin
  case(S)           // Описываем блок case, где значение сигнала S
                    // будет сравниваться с различными возможными его значениями
    1'b0: Y = D0;   // Если S==0, то Y = D0
    1'b1: Y = D1;
  endcase           // Каждый case должен заканчиваться endcase
end                 // (так же как каждый begin должен оканчиваться end)

Рассмотрим вариант посложнее и опишем следующую схему:

../.pic/Basic%20Verilog%20structures/multiplexors/fig_04.drawio.svg

Здесь уже используется мультиплексор 4в1. Управляющий сигнал S в данном случае двухбитный. В блоке case мы перечисляем всевозможные варианты значений S и описываем выход мультиплексора.

module case_mux_ex(
  input  logic        A,
  input  logic        B,
  input  logic        C,
  input  logic        D,
  input  logic [2:0]  S,

  output logic        Y

);
  always_comb begin
    case(S)
      3'b000:  Y = A;
      3'b001:  Y = C | B;      // в блоке case можно мультиплексировать
                               // не только провода, но и логические выражения
      3'b010:  Y = (C|B) & D;
      /*
        Обратите внимание, что разрядность сигнала S — 3 бита.
        Это означает, что есть 8 комбинаций его разрядов.
        Выше было описано только 3 комбинации из 8.
        Если для всех остальных комбинаций на выходе мультиплексора должно
        быть какое-то одно значение "по умолчанию", используется специальная
        комбинация "default":
      */
      default: Y = D;
    endcase
  end
endmodule

Оператор адресации

Представим, что нам необходимо мультиплексировать реально много сигналов. Например, отдельные биты 1024-разрядной шины. Описывать case на 1024 варианта будет сущим безумием. В этом случае, можно будет воспользоваться оператором '[]', который наверняка известен вам как "оператор адресации по массиву" в Си-подобных языках. Работает он интуитивно понятно:

  • перед оператором указывается имя массива или вектора (читай как "памяти или шины"), по которым будет идти индексация;
  • за именем в квадратных скобках указывается индекс (не важно, в виде константы, или выражения, использующего другие сигналы).

В контексте примера по мультиплексированию 1024 бит использование оператора может быть выполнено следующим образом:

logic [1023:0] bus1024;
logic [   9:0] select;

logic          one_bit_result;

assign one_bit_result = bus1024[select];

Реализация мультиплексоров через оператор '[]' будет активно применяться вами при реализации различных памятей.

Итоги

  1. Мультиплексор — это комбинационный блок, подающий на выход один из нескольких входных сигналов.
  2. Мультиплексор можно описать множеством способов, среди них:
    1. использование тернарного условного оператора;
    2. использование конструкции if-else внутри блока always;
    3. использование конструкции case внутри блока always;
    4. использование оператора '[]'.
  3. Во избежание появления защелок при описании мультиплексора, необходимо убедиться что у блоков if есть соответствующие им блоки else, а у мультиплексоров описаны все комбинации управляющего сигнала (при необходимости, множество оставшихся комбинаций можно покрыть с помощью комбинации default). Появление непреднамеренной защелки в дизайне ведет к ухудшению временных характеристик, избыточному использованию ресурсов, а также непредсказуемому поведению схемы из-за возможного удержания сигнала.
  4. Важно отметить, что блоки if-else и case могут использоваться не только для описания мультиплексоров.
  5. Конструкции if-else и case в рамках данных лабораторных работ можно описывать только внутри блока always. При работе с этим блоком необходимо помнить следующие особенности:
    1. Существует несколько типов блока always: always_comb, always_ff, always_latch, определяющих то, к чему будет подключена описанная в этом блоке логика: проводу, регистру или защелке соответственно. В данных лабораторных работах вам нужно будет пользоваться блоками always_ff и always_comb, причем:
      1. внутри блока always_ff необходимо использовать оператор неблокирующего присваивания (<=);
      2. внутри блока always_comb необходимо использовать оператор блокирующего присваивания (=).
    2. Присваивание для любого сигнала возможно только внутри одного блока always. Два разных сигнала могут присваиваться как в одном блоке always, так и каждый в отдельном, но операция присваивания одному и тому же сигналу в двух разных блоках always — нет.

Проверь себя

Как, по-вашему, описать на языке SystemVerilog схему, приведённую ниже?

../.pic/Basic%20Verilog%20structures/multiplexors/fig_05.drawio.svg